<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>The source code</title>
  <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
  <script type="text/javascript" src="../prettify/prettify.js"></script>
  <style type="text/css">
    .highlight { display: block; background-color: #ddd; }
  </style>
  <script type="text/javascript">
    function highlight() {
      document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
    }
  </script>
</head>
<body onload="prettyPrint(); highlight();">
  <pre class="prettyprint lang-js"><span id='Ext-data-TreeStore'>/**
</span> * The TreeStore is a store implementation that is backed by by an {@link Ext.data.Tree}.
 * It provides convenience methods for loading nodes, as well as the ability to use
 * the hierarchical tree structure combined with a store. This class is generally used
 * in conjunction with {@link Ext.tree.Panel}. This class also relays many events from
 * the Tree for convenience.
 * 
 * # Using Models
 * 
 * If no Model is specified, an implicit model will be created that implements {@link Ext.data.NodeInterface}.
 * The standard Tree fields will also be copied onto the Model for maintaining their state.
 * 
 * # Reading Nested Data
 * 
 * For the tree to read nested data, the {@link Ext.data.reader.Reader} must be configured with a root property,
 * so the reader can find nested data for each node. If a root is not specified, it will default to
 * 'children'.
 */
Ext.define('Ext.data.TreeStore', {
    extend: 'Ext.data.AbstractStore',
    alias: 'store.tree',
    requires: ['Ext.data.Tree', 'Ext.data.NodeInterface', 'Ext.data.NodeStore'],

<span id='Ext-data-TreeStore-cfg-root'>    /**
</span>     * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
     * The root node for this store. For example:
     * 
     *     root: {
     *         expanded: true, 
     *         text: &quot;My Root&quot;,
     *         children: [
     *             { text: &quot;Child 1&quot;, leaf: true },
     *             { text: &quot;Child 2&quot;, expanded: true, children: [
     *                 { text: &quot;GrandChild&quot;, leaf: true }
     *             ] }
     *         ]
     *     }
     * 
     * Setting the `root` config option is the same as calling {@link #setRootNode}.
     */

<span id='Ext-data-TreeStore-cfg-clearOnLoad'>    /**
</span>     * @cfg {Boolean} clearOnLoad
     * Remove previously existing child nodes before loading. Default to true.
     */
    clearOnLoad : true,

<span id='Ext-data-TreeStore-cfg-nodeParam'>    /**
</span>     * @cfg {String} nodeParam
     * The name of the parameter sent to the server which contains the identifier of the node.
     * Defaults to 'node'.
     */
    nodeParam: 'node',

<span id='Ext-data-TreeStore-cfg-defaultRootId'>    /**
</span>     * @cfg {String} defaultRootId
     * The default root id. Defaults to 'root'
     */
    defaultRootId: 'root',
    
<span id='Ext-data-TreeStore-cfg-defaultRootProperty'>    /**
</span>     * @cfg {String} defaultRootProperty
     * The root property to specify on the reader if one is not explicitly defined.
     */
    defaultRootProperty: 'children',

<span id='Ext-data-TreeStore-cfg-folderSort'>    /**
</span>     * @cfg {Boolean} folderSort
     * Set to true to automatically prepend a leaf sorter. Defaults to `undefined`.
     */
    folderSort: false,
    
    constructor: function(config) {
        var me = this, 
            root,
            fields;
        
        config = Ext.apply({}, config);
        
<span id='Ext-data-TreeStore-property-fields'>        /**
</span>         * If we have no fields declare for the store, add some defaults.
         * These will be ignored if a model is explicitly specified.
         */
        fields = config.fields || me.fields;
        if (!fields) {
            config.fields = [{name: 'text', type: 'string'}];
        }

        me.callParent([config]);
        
        // We create our data tree.
        me.tree = Ext.create('Ext.data.Tree');

        me.relayEvents(me.tree, [
<span id='Ext-data-TreeStore-event-append'>            /**
</span>             * @event append
             * Fires when a new child node is appended to a node in this store's tree.
             * @param {Tree} tree The owner tree
             * @param {Node} parent The parent node
             * @param {Node} node The newly appended node
             * @param {Number} index The index of the newly appended node
             */
            &quot;append&quot;,
            
<span id='Ext-data-TreeStore-event-remove'>            /**
</span>             * @event remove
             * Fires when a child node is removed from a node in this store's tree.
             * @param {Tree} tree The owner tree
             * @param {Node} parent The parent node
             * @param {Node} node The child node removed
             */
            &quot;remove&quot;,
            
<span id='Ext-data-TreeStore-event-move'>            /**
</span>             * @event move
             * Fires when a node is moved to a new location in the store's tree
             * @param {Tree} tree The owner tree
             * @param {Node} node The node moved
             * @param {Node} oldParent The old parent of this node
             * @param {Node} newParent The new parent of this node
             * @param {Number} index The index it was moved to
             */
            &quot;move&quot;,
            
<span id='Ext-data-TreeStore-event-insert'>            /**
</span>             * @event insert
             * Fires when a new child node is inserted in a node in this store's tree.
             * @param {Tree} tree The owner tree
             * @param {Node} parent The parent node
             * @param {Node} node The child node inserted
             * @param {Node} refNode The child node the node was inserted before
             */
            &quot;insert&quot;,
            
<span id='Ext-data-TreeStore-event-beforeappend'>            /**
</span>             * @event beforeappend
             * Fires before a new child is appended to a node in this store's tree, return false to cancel the append.
             * @param {Tree} tree The owner tree
             * @param {Node} parent The parent node
             * @param {Node} node The child node to be appended
             */
            &quot;beforeappend&quot;,
            
<span id='Ext-data-TreeStore-event-beforeremove'>            /**
</span>             * @event beforeremove
             * Fires before a child is removed from a node in this store's tree, return false to cancel the remove.
             * @param {Tree} tree The owner tree
             * @param {Node} parent The parent node
             * @param {Node} node The child node to be removed
             */
            &quot;beforeremove&quot;,
            
<span id='Ext-data-TreeStore-event-beforemove'>            /**
</span>             * @event beforemove
             * Fires before a node is moved to a new location in the store's tree. Return false to cancel the move.
             * @param {Tree} tree The owner tree
             * @param {Node} node The node being moved
             * @param {Node} oldParent The parent of the node
             * @param {Node} newParent The new parent the node is moving to
             * @param {Number} index The index it is being moved to
             */
            &quot;beforemove&quot;,
            
<span id='Ext-data-TreeStore-event-beforeinsert'>            /**
</span>             * @event beforeinsert
             * Fires before a new child is inserted in a node in this store's tree, return false to cancel the insert.
             * @param {Tree} tree The owner tree
             * @param {Node} parent The parent node
             * @param {Node} node The child node to be inserted
             * @param {Node} refNode The child node the node is being inserted before
             */
            &quot;beforeinsert&quot;,
             
<span id='Ext-data-TreeStore-event-expand'>             /**
</span>              * @event expand
              * Fires when this node is expanded.
              * @param {Node} this The expanding node
              */
             &quot;expand&quot;,
             
<span id='Ext-data-TreeStore-event-collapse'>             /**
</span>              * @event collapse
              * Fires when this node is collapsed.
              * @param {Node} this The collapsing node
              */
             &quot;collapse&quot;,
             
<span id='Ext-data-TreeStore-event-beforeexpand'>             /**
</span>              * @event beforeexpand
              * Fires before this node is expanded.
              * @param {Node} this The expanding node
              */
             &quot;beforeexpand&quot;,
             
<span id='Ext-data-TreeStore-event-beforecollapse'>             /**
</span>              * @event beforecollapse
              * Fires before this node is collapsed.
              * @param {Node} this The collapsing node
              */
             &quot;beforecollapse&quot;,

<span id='Ext-data-TreeStore-event-sort'>             /**
</span>              * @event sort
              * Fires when this TreeStore is sorted.
              * @param {Node} node The node that is sorted.
              */             
             &quot;sort&quot;,
             
<span id='Ext-data-TreeStore-event-rootchange'>             /**
</span>              * @event rootchange
              * Fires whenever the root node is changed in the tree.
              * @param {Ext.data.Model} root The new root
              */
             &quot;rootchange&quot;
        ]);

        me.tree.on({
            scope: me,
            remove: me.onNodeRemove,
            // this event must follow the relay to beforeitemexpand to allow users to
            // cancel the expand:
            beforeexpand: me.onBeforeNodeExpand,
            beforecollapse: me.onBeforeNodeCollapse,
            append: me.onNodeAdded,
            insert: me.onNodeAdded
        });

        me.onBeforeSort();

        root = me.root;
        if (root) {
            delete me.root;
            me.setRootNode(root);
        }
        
        me.addEvents(
<span id='Ext-data-TreeStore-event-rootchange'>            /**
</span>             * @event rootchange
             * Fires when the root node on this TreeStore is changed.
             * @param {Ext.data.TreeStore} store This TreeStore
             * @param {Node} The new root node.
             */
            'rootchange'
        );
        
        //&lt;deprecated since=0.99&gt;
        if (Ext.isDefined(me.nodeParameter)) {
            if (Ext.isDefined(Ext.global.console)) {
                Ext.global.console.warn('Ext.data.TreeStore: nodeParameter has been deprecated. Please use nodeParam instead.');
            }
            me.nodeParam = me.nodeParameter;
            delete me.nodeParameter;
        }
        //&lt;/deprecated&gt;
    },
    
    // inherit docs
    setProxy: function(proxy) {
        var reader,
            needsRoot;
        
        if (proxy instanceof Ext.data.proxy.Proxy) {
            // proxy instance, check if a root was set
            needsRoot = Ext.isEmpty(proxy.getReader().root);
        } else if (Ext.isString(proxy)) {
            // string type, means a reader can't be set
            needsRoot = true;
        } else {
            // object, check if a reader and a root were specified.
            reader = proxy.reader;
            needsRoot = !(reader &amp;&amp; !Ext.isEmpty(reader.root));
        }
        proxy = this.callParent(arguments);
        if (needsRoot) {
            reader = proxy.getReader();
            reader.root = this.defaultRootProperty;
            // force rebuild
            reader.buildExtractors(true);
        }
    },
    
    // inherit docs
    onBeforeSort: function() {
        if (this.folderSort) {
            this.sort({
                property: 'leaf',
                direction: 'ASC'
            }, 'prepend', false);    
        }
    },
    
<span id='Ext-data-TreeStore-method-onBeforeNodeExpand'>    /**
</span>     * Called before a node is expanded.
     * @private
     * @param {Ext.data.NodeInterface} node The node being expanded.
     * @param {Function} callback The function to run after the expand finishes
     * @param {Object} scope The scope in which to run the callback function
     */
    onBeforeNodeExpand: function(node, callback, scope) {
        if (node.isLoaded()) {
            Ext.callback(callback, scope || node, [node.childNodes]);
        }
        else if (node.isLoading()) {
            this.on('load', function() {
                Ext.callback(callback, scope || node, [node.childNodes]);
            }, this, {single: true});
        }
        else {
            this.read({
                node: node,
                callback: function() {
                    Ext.callback(callback, scope || node, [node.childNodes]);
                }
            });            
        }
    },
    
    //inherit docs
    getNewRecords: function() {
        return Ext.Array.filter(this.tree.flatten(), this.filterNew);
    },

    //inherit docs
    getUpdatedRecords: function() {
        return Ext.Array.filter(this.tree.flatten(), this.filterUpdated);
    },
    
<span id='Ext-data-TreeStore-method-onBeforeNodeCollapse'>    /**
</span>     * Called before a node is collapsed.
     * @private
     * @param {Ext.data.NodeInterface} node The node being collapsed.
     * @param {Function} callback The function to run after the collapse finishes
     * @param {Object} scope The scope in which to run the callback function
     */
    onBeforeNodeCollapse: function(node, callback, scope) {
        callback.call(scope || node, node.childNodes);
    },
    
    onNodeRemove: function(parent, node) {
        var removed = this.removed;
        
        if (!node.isReplace &amp;&amp; Ext.Array.indexOf(removed, node) == -1) {
            removed.push(node);
        }
    },
    
    onNodeAdded: function(parent, node) {
        var proxy = this.getProxy(),
            reader = proxy.getReader(),
            data = node.raw || node.data,
            dataRoot, children;
            
        Ext.Array.remove(this.removed, node); 
        
        if (!node.isLeaf() &amp;&amp; !node.isLoaded()) {
            dataRoot = reader.getRoot(data);
            if (dataRoot) {
                this.fillNode(node, reader.extractData(dataRoot));
                delete data[reader.root];
            }
        }
    },
        
<span id='Ext-data-TreeStore-method-setRootNode'>    /**
</span>     * Sets the root node for this store.  See also the {@link #root} config option.
     * @param {Ext.data.Model/Ext.data.NodeInterface/Object} root
     * @return {Ext.data.NodeInterface} The new root
     */
    setRootNode: function(root) {
        var me = this;

        root = root || {};        
        if (!root.isNode) {
            // create a default rootNode and create internal data struct.        
            Ext.applyIf(root, {
                id: me.defaultRootId,
                text: 'Root',
                allowDrag: false
            });
            root = Ext.ModelManager.create(root, me.model);
        }
        Ext.data.NodeInterface.decorate(root);

        // Because we have decorated the model with new fields,
        // we need to build new extactor functions on the reader.
        me.getProxy().getReader().buildExtractors(true);
        
        // When we add the root to the tree, it will automaticaly get the NodeInterface
        me.tree.setRootNode(root);
        
        // If the user has set expanded: true on the root, we want to call the expand function
        if (!root.isLoaded() &amp;&amp; root.isExpanded()) {
            me.load({
                node: root
            });
        }
        
        return root;
    },
        
<span id='Ext-data-TreeStore-method-getRootNode'>    /**
</span>     * Returns the root node for this tree.
     * @return {Ext.data.NodeInterface}
     */
    getRootNode: function() {
        return this.tree.getRootNode();
    },

<span id='Ext-data-TreeStore-method-getNodeById'>    /**
</span>     * Returns the record node by id
     * @return {Ext.data.NodeInterface}
     */
    getNodeById: function(id) {
        return this.tree.getNodeById(id);
    },

<span id='Ext-data-TreeStore-method-load'>    /**
</span>     * Loads the Store using its configured {@link #proxy}.
     * @param {Object} options Optional config object. This is passed into the {@link Ext.data.Operation Operation}
     * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function.
     * The options can also contain a node, which indicates which node is to be loaded. If not specified, it will
     * default to the root node.
     */
    load: function(options) {
        options = options || {};
        options.params = options.params || {};
        
        var me = this,
            node = options.node || me.tree.getRootNode(),
            root;
            
        // If there is not a node it means the user hasnt defined a rootnode yet. In this case lets just
        // create one for them.
        if (!node) {
            node = me.setRootNode({
                expanded: true
            });
        }
        
        if (me.clearOnLoad) {
            node.removeAll();
        }
        
        Ext.applyIf(options, {
            node: node
        });
        options.params[me.nodeParam] = node ? node.getId() : 'root';
        
        if (node) {
            node.set('loading', true);
        }
        
        return me.callParent([options]);
    },
        

<span id='Ext-data-TreeStore-method-fillNode'>    /**
</span>     * Fills a node with a series of child records.
     * @private
     * @param {Ext.data.NodeInterface} node The node to fill
     * @param {Array} records The records to add
     */
    fillNode: function(node, records) {
        var me = this,
            ln = records ? records.length : 0,
            i = 0, sortCollection;

        if (ln &amp;&amp; me.sortOnLoad &amp;&amp; !me.remoteSort &amp;&amp; me.sorters &amp;&amp; me.sorters.items) {
            sortCollection = Ext.create('Ext.util.MixedCollection');
            sortCollection.addAll(records);
            sortCollection.sort(me.sorters.items);
            records = sortCollection.items;
        }
        
        node.set('loaded', true);
        for (; i &lt; ln; i++) {
            node.appendChild(records[i], undefined, true);
        }
        
        return records;
    },

    // inherit docs
    onProxyLoad: function(operation) {
        var me = this,
            successful = operation.wasSuccessful(),
            records = operation.getRecords(),
            node = operation.node;

        node.set('loading', false);
        if (successful) {
            records = me.fillNode(node, records);
        }
        // deprecate read?
        me.fireEvent('read', me, operation.node, records, successful);
        me.fireEvent('load', me, operation.node, records, successful);
        //this is a callback that would have been passed to the 'read' function and is optional
        Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
    },
    
<span id='Ext-data-TreeStore-method-onCreateRecords'>    /**
</span>     * Creates any new records when a write is returned from the server.
     * @private
     * @param {Array} records The array of new records
     * @param {Ext.data.Operation} operation The operation that just completed
     * @param {Boolean} success True if the operation was successful
     */
    onCreateRecords: function(records, operation, success) {
        if (success) {
            var i = 0,
                length = records.length,
                originalRecords = operation.records,
                parentNode,
                record,
                original,
                index;

            /*
             * Loop over each record returned from the server. Assume they are
             * returned in order of how they were sent. If we find a matching
             * record, replace it with the newly created one.
             */
            for (; i &lt; length; ++i) {
                record = records[i];
                original = originalRecords[i];
                if (original) {
                    parentNode = original.parentNode;
                    if (parentNode) {
                        // prevent being added to the removed cache
                        original.isReplace = true;
                        parentNode.replaceChild(record, original);
                        delete original.isReplace;
                    }
                    record.phantom = false;
                }
            }
        }
    },

<span id='Ext-data-TreeStore-method-onUpdateRecords'>    /**
</span>     * Updates any records when a write is returned from the server.
     * @private
     * @param {Array} records The array of updated records
     * @param {Ext.data.Operation} operation The operation that just completed
     * @param {Boolean} success True if the operation was successful
     */
    onUpdateRecords: function(records, operation, success){
        if (success) {
            var me = this,
                i = 0,
                length = records.length,
                data = me.data,
                original,
                parentNode,
                record;

            for (; i &lt; length; ++i) {
                record = records[i];
                original = me.tree.getNodeById(record.getId());
                parentNode = original.parentNode;
                if (parentNode) {
                    // prevent being added to the removed cache
                    original.isReplace = true;
                    parentNode.replaceChild(record, original);
                    original.isReplace = false;
                }
            }
        }
    },

<span id='Ext-data-TreeStore-method-onDestroyRecords'>    /**
</span>     * Removes any records when a write is returned from the server.
     * @private
     * @param {Array} records The array of removed records
     * @param {Ext.data.Operation} operation The operation that just completed
     * @param {Boolean} success True if the operation was successful
     */
    onDestroyRecords: function(records, operation, success){
        if (success) {
            this.removed = [];
        }
    },

    // inherit docs
    removeAll: function() {
        this.getRootNode().destroy(true);
        this.fireEvent('clear', this);
    },

    // inherit docs
    doSort: function(sorterFn) {
        var me = this;
        if (me.remoteSort) {
            //the load function will pick up the new sorters and request the sorted data from the proxy
            me.load();
        } else {
            me.tree.sort(sorterFn, true);
            me.fireEvent('datachanged', me);
        }   
        me.fireEvent('sort', me);
    }
});</pre>
</body>
</html>
